package com.wdb007.venus.wss.handler;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.IOUtils;
import com.wdb007.venus.svc.biz.manager.ConfigInfoManager;
import com.wdb007.venus.wss.channelgroup.AppChannelGroups;
import com.wdb007.venus.wss.channelgroup.BookCaseChannelGroups;
import com.wdb007.venus.wss.channelgroup.ChannelGroups;
import com.wdb007.venus.wss.constants.PushActionEnum;
import com.wdb007.venus.wss.factory.WebSocketHandlerFactory;
import com.wdb007.venus.wss.utils.MD5Util;
import com.wdb007.venus.wss.utils.StringUtil;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;

@Sharable
@Scope("prototype")
@Service("websocketServerHandler")
public class WebsocketServerHandler extends SimpleChannelInboundHandler<Object> {

	private static Logger log = LoggerFactory.getLogger(WebsocketServerHandler.class);

	private WebSocketServerHandshaker handshaker;

	@Autowired(required = false)
	private WebSocketHandlerFactory webSocketHandlerFactory;
	@Autowired
	private ConfigInfoManager configInfoManager;
	
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
		
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		if (ctx != null) {
			log.error("error when proccessing websocket request", cause);
			try {
				log.info("start to do business service close process...");
				webSocketHandlerFactory.close(ctx, cause);
			} catch (Exception e) {
				log.error("error in webSocketServerService close method", e);
			}
			log.info("start to check channel is active...");
			if (ctx.channel().isActive()) {
				ctx.close();
				log.info("channel is active, close it...");
			} else
				log.info("channel is not active, deal over...");
		}
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		if(ChannelGroups.contains(channel)) {
			ChannelGroups.discard(channel);
		}else if(BookCaseChannelGroups.contains(channel)) {
			BookCaseChannelGroups.discard(channel);
		}
	}
	
	/**
	 * 
	 * @Title: handleHttpRequest
	 * @Description: 处理HTTP请求
	 * @param @param
	 *            ctx
	 * @param @param
	 *            req
	 * @param @throws
	 *            Exception
	 * @return void
	 * @throws @author
	 *             fengjun
	 * @date 2015年5月29日 下午4:34:15
	 */
	private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
		// 如果HTTP解码失败，返回HTTP异常
		if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
			log.debug("HTTP解码失败，返回HTTP异常");
			sendHttpResponse(ctx, req,
					new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
			return;
		}
		log.info("uri: " + req.uri());
		log.info("X-Real-IP: {}" , req.headers().get("X-Real-IP"));
		Map<String, String> queryMap = handleQueryString(req);
		String server = queryMap.get("action");
		String bookcaseId = queryMap.get("bookCaseId");
		if(null == server)
			server = queryMap.get("type");
		boolean checkToken = false ;
		if("server".equalsIgnoreCase(server)) {
			//校验应用服务器端发送的url连接是否正确
			checkToken = checkServerParams(queryMap);
			if(checkToken)
				ChannelGroups.add(ctx.channel());
		}else if("bookcase".equalsIgnoreCase(server)) {
			//校验书柜发送的url连接是否正确
			checkToken = checkBookCaseParams(queryMap);
			if(checkToken)
				BookCaseChannelGroups.add(bookcaseId, ctx.channel());
		}else if("app".equalsIgnoreCase(server)) {
			//校验书柜发送的url连接是否正确
			checkToken = checkAppParams(queryMap);
			String userId = queryMap.get("userid");
			if(checkToken)
				AppChannelGroups.add(userId, ctx.channel());
		}else {
			log.error("uri中没有action参数或者参数不合法, action:[{}]" , server);
			sendHttpResponse(ctx, req,
					new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
			return;
		}
		if(!checkToken) {
			log.error("token校验失败");
			sendHttpResponse(ctx, req,
					new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
			return;
		}
		// 构造握手响应返回
		WebSocketServerHandshakerFactory wsShakerFactory = new WebSocketServerHandshakerFactory(
				"ws://" + req.headers().get(HttpHeaderNames.HOST), null, false);
		handshaker = wsShakerFactory.newHandshaker(req);
		if (null == handshaker) {
			// 无法处理的websocket版本
			log.debug("无法处理的websocket版本");
			WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
		} else {
			// 向客户端发送websocket握手,完成握手
			// 客户端收到的状态是101 switching protocol
			log.debug("客户端发送websocket握手,完成握手");
			Iterator it = req.headers().iteratorCharSequence();
			if (it.hasNext()) {
				it.next();
			}

			handshaker.handshake(ctx.channel(), req);

			for (String pro : handshaker.subprotocols()) {
				log.debug("protocols:" + pro);
			}

			log.debug("WS version=" + handshaker.version());
			
			//首次连接时 下发策略以及下发二维码
			if(BookCaseChannelGroups.contains(ctx.channel())) {
//				policiesIssued(ctx);
				//修改下发策略
				JSONObject piJSON = new JSONObject();
				piJSON.put("action", PushActionEnum.policies_issued.name());
				piJSON.put("bookCaseId", bookcaseId);
				webSocketHandlerFactory.handle(ctx, piJSON.toJSONString());
				JSONObject jsonObj = new JSONObject();
				jsonObj.put("action", PushActionEnum.policies_qrcode.name());
				jsonObj.put("bookCaseId", bookcaseId);
				webSocketHandlerFactory.handle(ctx, jsonObj.toJSONString());
			}
		}

	}
	/**
	 * 书柜每一次连接推送服务器时，推送服务器会给书柜下发策略
	 * @param ctx
	 */
	private void policiesIssued(ChannelHandlerContext ctx) {
		String action = PushActionEnum.policies_issued.name();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("action", action);
		map.put("versions", configInfoManager.getValue("versions"));
		map.put("http_url", configInfoManager.getValue("http_url"));
		map.put("web_url", configInfoManager.getValue("web_url"));
		map.put("text", configInfoManager.getValue("text"));
		
		Map<String, Object> innerMap = new HashMap<String, Object>();
		String statusCheck = configInfoManager.getValue("statusCheck");
		String bookCheck = configInfoManager.getValue("bookCheck");
		String[] statusCheckArr = statusCheck.split(",");
		List<String> statusList = Arrays.asList(statusCheckArr);
		String[] bookCheckArr = bookCheck.split(",");
		List<String> checkList = Arrays.asList(bookCheckArr);
		innerMap.put("interval", Integer.valueOf(configInfoManager.getValue("interval")));
		innerMap.put("statusCheck", statusList);
		innerMap.put("bookCheck", checkList);
		innerMap.put("reserve", configInfoManager.getValue("reserve"));
		
		String uvlamp_start = configInfoManager.getValue("uvlamp_start");
		String[] uvlamp_startArr = uvlamp_start.split(",");

		List<String> uvlamp_startList = Arrays.asList(uvlamp_startArr);	
		
		String led_start_end = configInfoManager.getValue("led_start_end");
		String[] led_start_endArr = led_start_end.split(",");
		List<String> led_start_endArrList = Arrays.asList(led_start_endArr);	
		
		innerMap.put("uvlamp_start", uvlamp_startList);
		innerMap.put("led_start_end", led_start_endArrList);
		innerMap.put("uvlamp_keep", Integer.valueOf(configInfoManager.getValue("uvlamp_keep")));
		map.put("info", innerMap);
		String jsonStr = JSON.toJSONString(map);
		ctx.channel().writeAndFlush(new TextWebSocketFrame(jsonStr));
	}
	/**
	 * 校验书柜传递过来的url参数是否合法
	 * @param queryMap
	 * 
	 * @return
	 */
	private boolean checkBookCaseParams(Map<String, String> queryMap) {
		if(!queryMap.containsKey("bookCaseId") || !queryMap.containsKey("token") || !queryMap.containsKey("timeStamp")) {
			log.error("参数bookCaseId或者token或者timeStamp不能为空");
			return false;
		}
		
		String bookCaseId = queryMap.get("bookCaseId");
		String token = queryMap.get("token");
		String timeStamp = queryMap.get("timeStamp");
		
		//2.校验md5
		String secretKeyPath = configInfoManager.getValue("secretKey_path");
		String path = secretKeyPath + File.separator + bookCaseId.substring(0, 4) + File.separator + bookCaseId + "_secretKey.dat";
		File file = new File(path);
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
			String secretKey = br.readLine();
			String verify_token = MD5Util.getMD5(secretKey+timeStamp);
//			if(!token.equalsIgnoreCase(verify_token)) {
//				log.error("书柜token[{}]与推送服务器生成的token[{}]不一致，校验失败", token, verify_token);
//				return false;
//			}
		} catch (UnsupportedEncodingException | FileNotFoundException e) {
			log.error("文件查找不到", e);
			return false;
		} catch (IOException e) {
			log.error("IO发生异常", e);
			return false;
		}finally {
			IOUtils.close(br);
		}
		return true;
	}
	/**
	 * 校验服务端传递过来的参数是否合法
	 * @param queryMap
	 * @return
	 */
	private boolean checkServerParams(Map<String, String> queryMap) {
		if(!queryMap.containsKey("server") || !queryMap.containsKey("token")) {
			log.error("参数server或者token不能为空");
			return false;
		}
		String serverName = queryMap.get("server");
//		String token = queryMap.get("token");
//		String verify_token = WsStrUtils.getServerToken(serverName);
//		if(!token.equalsIgnoreCase(verify_token))
//			return false;
		return true;
	}
	/**
	 * 校验书柜传递过来的url参数是否合法
	 * @param queryMap
	 * 
	 * @return
	 */
	private boolean checkAppParams(Map<String, String> queryMap) {
		if(!queryMap.containsKey("userid") || !queryMap.containsKey("token")) {
			log.error("参数userid或者token不能为空");
			return false;
		}
		//FIXME token待校验
		return true;
	}
	/**
	 * 
	 * @Title: handleWebSocketFrame
	 * @Description: 处理websocket消息
	 * @param @param
	 *            ctx
	 * @param @param
	 *            frame
	 * @param @throws
	 *            Exception
	 * @return void
	 * @throws @author
	 *             fengjun
	 * @date 2015年5月29日 下午4:34:52
	 */
	private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
		Channel channel = ctx.channel();
		// 判断是否是关闭链路的指令
		if (frame instanceof CloseWebSocketFrame) {
			ChannelGroups.discard(channel);
			handshaker.close(channel, (CloseWebSocketFrame) frame.retain());
			return;
		}

		// 判断是否是ping消息
		if (frame instanceof PingWebSocketFrame) {
			channel.write(new PongWebSocketFrame(frame.content().retain()));
			return;
		}

		// TODO 目前仅支持文本消息，不支持二进制消息
		if (frame instanceof TextWebSocketFrame) {
			// 业务请求返回消息处理
			String request = ((TextWebSocketFrame) frame).text();
			if (!StringUtil.isEmpty(request)) {
				if("HeartBeat".equals(request)) {
					channel.writeAndFlush(new TextWebSocketFrame("HeartBeatResponse"));
					return ;
				}
				webSocketHandlerFactory.handle(ctx, request);
			}
		} else {
			throw new UnsupportedOperationException(
					String.format("%s frame types not supported", frame.getClass().getName()));
		}
		
	}

	/**
	 * 
	 * @Title: sendHttpResponse
	 * @Description: 返回http握手的结果
	 * @param @param
	 *            ctx
	 * @param @param
	 *            req
	 * @param @param
	 *            res
	 * @return void
	 * @throws @author
	 *             fengjun
	 * @date 2015年6月15日 下午5:55:29
	 */
	private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
		// 返回应答给客户端
		if (res.status().code() != 200) {
			ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
			res.content().writeBytes(buf);
			buf.release();
			HttpUtil.setContentLength(res, res.content().readableBytes());
		}

		// 如果是非keep-alive，关闭连接
		ChannelFuture f = ctx.channel().writeAndFlush(res);
		if (!isKeepAlive(req) || res.status().code() != 200) {
			try {
				log.info("this request is not keepalive, start to close it..");
				webSocketHandlerFactory.close(ctx, new Throwable("for it is un keepAlive connection"));
			} catch (Exception e) {
				log.error("error in webSocketServerService close method", e);
			}
			f.addListener(ChannelFutureListener.CLOSE);
		}
	}

	/**
	 * 
	 * @Title: isKeepAlive
	 * @Description: 判断HTTP请求是否为keep-alive
	 * @param @param
	 *            req
	 * @param @return
	 * @return boolean
	 * @throws @author
	 *             fengjun
	 * @date 2015年5月29日 下午4:35:16
	 */
	private static boolean isKeepAlive(FullHttpRequest req) {
		boolean keepAlive = !req.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)
				&& (!req.protocolVersion().equals(HttpVersion.HTTP_1_0)
						|| req.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true));
		return keepAlive;
	}

	private Map<String, String> handleQueryString(FullHttpRequest req) {

		Map<String, String> map = new HashMap<String, String>();
		String uri = req.uri();
		String queryString = uri.substring(uri.indexOf("?"));
		if (null != queryString && queryString.length() > 2) {
			queryString = queryString.substring(1);
			String[] arr = queryString.split("&");
			for (String s : arr) {
				int index = s.indexOf("=");
				String key = s.substring(0, index);
				String value = s.substring(index + 1);
				map.put(key, value);
			}
		}
		return map;
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 虽然是websocket，但在建立websocket连接前，先进行http握手,所以，这时也要处理http请求
		// 在http握手完成后，才是websocket下的通信
		if (msg instanceof FullHttpRequest) {
			handleHttpRequest(ctx, (FullHttpRequest) msg);
		}
		// websocket接入
		else if (msg instanceof WebSocketFrame) {
			handleWebSocketFrame(ctx, (WebSocketFrame) msg);
		}

	}
}
